package com.zx.zxapibackend.utils;

import cn.hutool.core.lang.UUID;
import com.zx.zxapicommon.constant.RabbitmqConstant;
import com.zx.zxapicommon.constant.RedisConstant;
import com.zx.zxapicommon.model.dto.SmsDTO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.core.ReturnedMessage;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;

import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;


/**
 * @author 张旭
 * @version 1.0
 */
@Component
@Slf4j
public class SmsRabbitmqUtils implements RabbitTemplate.ConfirmCallback, RabbitTemplate.ReturnsCallback {

    @Autowired
    private RedisTemplate redisTemplate;

    @Resource
    private RabbitTemplate rabbitTemplate;

    private String finalId = null;

    private SmsDTO smsDTO = null;

    /**
     * 向mq中投递发送短信消息
     *
     * @param smsDTO
     */
    public void sendSmsAsync(SmsDTO smsDTO) {
        //短信id
        String messageId = null;

        try {
            //将 headers 添加到 MessageProperties 中，并发送消息
            messageId = UUID.randomUUID().toString();
            Map<String, Object> messageArgs = new HashMap<>();
            //将重试次数retryCount、状态status、短信类smsDTO
            messageArgs.put("retryCount", 0);
            //消息状态：0-未投递 1-已投递
            messageArgs.put("status", 0);
            messageArgs.put("smsDTO", smsDTO);
            //将重试次数和短信发送状态存入redis中，并设置过期时间
            redisTemplate.opsForHash().putAll(RedisConstant.SMS_MESSAGE_PREFIX + messageId, messageArgs);
            redisTemplate.expire(RedisConstant.SMS_MESSAGE_PREFIX + messageId, 10, TimeUnit.MINUTES);

            //消息全局唯一ID
            String finaleMessageId = messageId;
            finalId = messageId;
            this.smsDTO = smsDTO;

            //将消息投递到MQ，并设置消息的一些参数
            rabbitTemplate.convertAndSend(RabbitmqConstant.SMS_EXCHANGE_NAME, RabbitmqConstant.SMS_EXCHANGE_ROUTING_KEY,
                    smsDTO, message -> {
                        MessageProperties messageProperties = message.getMessageProperties();
                        //设置全局唯一id
                        messageProperties.setMessageId(finaleMessageId);
                        messageProperties.setContentEncoding("utf-8");
                        return message;
                    }
            );
        } catch (Exception e) {
            //出现异常，删除该短信id对应的redis，并将该失败消息存入到“死信”redis中
            redisTemplate.delete(RedisConstant.SMS_MESSAGE_PREFIX + messageId);
            redisTemplate.opsForHash().put(RedisConstant.MQ_PRODUCER, messageId, smsDTO);
            //后续使用定时任务去扫描该key，并重新发送到mq中去
            throw new RuntimeException(e);
        }

    }

    /**
     * 发布者确认的回调
     *
     * @param correlationData 回调相关数据
     * @param b               ack为真，nack为假
     * @param s               如果发生错误，为错误原因
     */
    @Override
    public void confirm(CorrelationData correlationData, boolean b, String s) {
        if (b) {
            //消息发送成功，将redis中的信息状态（status）修改为1
            redisTemplate.opsForHash().put(RedisConstant.SMS_MESSAGE_PREFIX + finalId, "status", 1);
        } else {
            //发送失败，放入redis失败集合，并删除集合数据
            log.error("短息投递失败：{}————>{}", correlationData, s);
            redisTemplate.delete(RedisConstant.SMS_MESSAGE_PREFIX + finalId);
            redisTemplate.opsForHash().put(RedisConstant.MQ_PRODUCER, finalId, this.smsDTO);
        }
    }

    /**
     * 发生异常时的消息返回信息
     *
     * @param returnedMessage
     */
    @Override
    public void returnedMessage(ReturnedMessage returnedMessage) {
        log.error("发生异常，返回消息回调：{}", returnedMessage);
        //发送失败，删除集合数据，并放入“死信”redis中
        redisTemplate.delete(RedisConstant.SMS_MESSAGE_PREFIX + finalId);
        redisTemplate.opsForHash().put(RedisConstant.MQ_PRODUCER, finalId, this.smsDTO);
    }


    @PostConstruct
    public void init() {
        rabbitTemplate.setConfirmCallback(this);
        rabbitTemplate.setReturnsCallback(this);
    }

}
